package com.terrier.ui.controls.supportClasses
{
    import com.terrier.ui.core.WxComponent;
    import com.terrier.ui.events.WxRangeEvent;

    /**
     * 当<code>value</code>属性改变时调度
     * @eventType com.monkey.ui.events.WxRangeEvent.VALUE_CHANGE
     */
    [Event(name = "valueChange", type = "com.terrier.ui.events.WxRangeEvent")]

    /**
     * Range 类包含一个值和一个该值所允许的范围，它们由 <code>minimum</code> 和 <code>maximum</code> 属性定义。
     * 会始终将 <code>value</code> 属性约束为处于当前 <code>minimum</code> 和 <code>maximum</code> 之间，
     * 且始终将 <code>minimum</code> 和 <code>maximum</code> 约束为按照正确的数字顺序，
     * 从而使 <code>(minimum &lt;= value &lt;= maximum)</code> 为 <code>true</code>。
     * 如果 <code>snapInterval</code> 属性的值不为 0，则也会将 <code>value</code> 属性约束为 <code>snapInterval</code> 的倍数。
     * Range 是需要 range 功能的各种控件（包括 WxTrackBase 和WxSpinner）的基类。
     *
     * @author Jarvis.weng
     */
    public class WxRange extends WxComponent
    {
        private static const DEFAULT_SNAPINTERVAL:Number = 1;

        private var _value:Number = 0;

        private var _minimum:Number = 0;

        private var _maximum:Number = 100;

        private var _snapInterval:Number = DEFAULT_SNAPINTERVAL;

        private var _explicitSnapInterval:Boolean = false;

        private var _stepSize:Number = 1;

        /**
         * 构造函数
         */
        public function WxRange()
        {
            super();
        }

        /**
         * 当前<code>value</code>值和<code>range</code>值的百分比。
         * 例如：如果minimum为30，maximum为130，value为80，则此属性为0.5。
         * @return
         */
        public function get percentValue():Number
        {
            var range:Number = maximum - minimum;
            return range > 0 ? (value - minimum) / range : 0;
        }

        /**
         * 最大值和最小值之间的范围.
         */
        public function get range():Number
        {
            return maximum - minimum;
        }

        /**
         * 调用 changeValueByStep() 方法时 value 属性更改的数量。
         * 除非 snapInterval 为 0，否则它必须是 snapInterval 的倍数。如果 stepSize 不是倍数，则会将它近似到大于或等于 snapInterval 的最近的倍数。
         */
        public function get stepSize():Number
        {
            return _stepSize;
        }

        /**
         * @private
         */
        public function set stepSize(value:Number):void
        {
            if (value < 0)
            {
                throw new Error("The value of stepSize must be greater than or equal to 0");
            }

            if (value == _stepSize)
            {
                return;
            }

            _stepSize = value;

            if (!_explicitSnapInterval && !isNaN(value) && _snapInterval != value)
            {
                _snapInterval = value;
                constrainProperties(false, false, true);
            }
        }

        /**
         * 最小有效 value。
         * @default 0
         */
        public function get minimum():Number
        {
            return _minimum;
        }

        /**
         * @private
         */
        public function set minimum(value:Number):void
        {
            if (value == _minimum)
            {
                return;
            }

            _minimum = value;

            constrainProperties(true, false, false);
        }

        /**
         * 最大有效 value。
         * @default 100
         */
        public function get maximum():Number
        {
            return _maximum;
        }

        /**
         * @private
         */
        public function set maximum(value:Number):void
        {
            if (value == _maximum)
            {
                return;
            }

            _maximum = value;

            constrainProperties(false, true, false);
        }

        /**
         * snapInterval 属性控制 value 属性的有效值。如果为非零，则value的有效值为 minimum 与此属性的整数倍数之和，且小于或等于 maximum。
         * 例如，如果 minimum 为 10，maximum 为 20，而此属性为 3，则可能的有效值为 10、13、16、19 和 20。
         * 如果此属性的值为零，则仅会将有效值约束到介于 minimum 和 maximum 之间（包括两者）。
         * 此属性还约束 stepSize 属性（如果设置）的有效值。如果未显式设置此属性，但设置了 stepSize，则 snapInterval 将默认为 stepSize。
         * @default 1。
         */
        public function get snapInterval():Number
        {
            return _snapInterval;
        }

        /**
         * @private
         */
        public function set snapInterval(value:Number):void
        {
            _explicitSnapInterval = true;
            if (isNaN(value))
            {
                value = DEFAULT_SNAPINTERVAL;
                _explicitSnapInterval = false;
            }

            if (value < 0)
            {
                throw new RangeError("The value of snapInterval must be greater than or equal to 0");
            }

            if (value == _snapInterval)
            {
                return;
            }

            _snapInterval = value;

//            constrainProperties(false, false, true);
        }

        /**
         *  此范围的当前值。
         *  value 属性的更改约束为大于或等于 minimum 属性，小于或等于 maximum 属性，以及 snapInterval 的倍数。
         *  @default 0
         */
        public function get value():Number
        {
            return _value;
        }

        /**
         * @private
         */
        public function set value(newValue:Number):void
        {
            newValue = nearestValidValue(minimum, maximum, newValue, snapInterval);

            if (newValue == _value)
            {
                return;
            }

            var oldValue:Number = _value;
            _value = newValue;

            dispatchEvent(new WxRangeEvent(WxRangeEvent.VALUE_CHANGE, oldValue, newValue));
        }

        //--------------------------------------------------------------------------
        //
        //  Methods
        //
        //--------------------------------------------------------------------------

        /**
         * 按 <code>stepSize</code> 增大或减小 value。
         * @param increase 若为 true，则向 value 增加 stepSize，否则减去它。
         */
        public function changeValueByStep(increase:Boolean = true):void
        {
            if (snapInterval == 0)
                return;

            var newValue:Number = (increase) ? value + snapInterval : value - snapInterval;

            value = newValue;
        }

        //--------------------------------------------------
        //
        // Protected Methods
        //
        //--------------------------------------------------

        /**
         * @private
         */
        protected function constrainProperties(minimumChanged:Boolean,
                                               maximumChanged:Boolean,
                                               snapIntervalChanged:Boolean):void
        {
            if (_minimum > _maximum)
            {
                if (maximumChanged)
                {
                    _minimum = _maximum;
                }
                else
                {
                    _maximum = _minimum;
                }
            }

            if (_value < _minimum || _value > _maximum || snapIntervalChanged)
            {
                _value = nearestValidValue(_minimum, _maximum, _value, _snapInterval);
            }
        }

        //--------------------------------------------------
        //
        // Private Methods
        //
        //--------------------------------------------------

        private function getPrecision(value:Number):int
        {
            const parts:Array = value.toString().split(".");
            return parts.length > 1 ? parts[1].length : 0;
        }

        private function nearestValidValue(minimum:Number, maximum:Number, newValue:Number, interval:Number):Number
        {
            if (interval == 0)
            {
                return Math.max(minimum, Math.min(maximum, newValue));
            }

            if (newValue <= minimum)
            {
                return minimum;
            }
            else if (newValue >= maximum)
            {
                return maximum;
            }

            newValue = Math.max(newValue - minimum, 0);
            var maxValue:Number = maximum - minimum;

            var validValue:Number = Math.min(Math.round(newValue / interval) * interval, maxValue);
            var precision:int = Math.max(getPrecision(interval), getPrecision(minimum), getPrecision(maximum));
            var scale:Number = Math.pow(10, precision);

            return Math.round((validValue + minimum) * scale) / scale;
        }

    }
}
